namespace EyeSoft.Wpf.Facilities.Collections.ObjectModel
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.Collections.Specialized;
	using System.Linq.Expressions;
	using System.Windows.Threading;

	public class SmartObservableCollection<T> : ObservableCollection<T>, IEnumerable<T>
	{
		protected readonly object instance = new object();

		/// <summary>
		/// Initializes a new instance of the SmartObservableCollection class.
		/// </summary>
		public SmartObservableCollection()
		{
		}

		public SmartObservableCollection(IEnumerable<T> collection)
			: base(collection)
		{
		}

		public SmartObservableCollection(List<T> collection)
			: base(collection)
		{
		}

		public SmartObservableCollection(IComparer<T> comparer)
		{
			this.Sort(comparer);
		}

		public SmartObservableCollection(IEnumerable<T> collection, IComparer<T> comparer)
			: base(collection)
		{
			this.Sort(comparer);
		}

		public SmartObservableCollection(List<T> collection, IComparer<T> comparer)
			: base(collection)
		{
			this.Sort(comparer);
		}

		public SmartObservableCollection(Expression<Func<T, IComparable>> propertyExpression)
		{
			this.Sort(propertyExpression);
		}

		public SmartObservableCollection(IEnumerable<T> collection, Expression<Func<T, IComparable>> propertyExpression)
			: base(collection)
		{
			this.Sort(propertyExpression);
		}

		public SmartObservableCollection(List<T> collection, Expression<Func<T, IComparable>> propertyExpression)
			: base(collection)
		{
			this.Sort(propertyExpression);
		}

		public SmartObservableCollection(Func<T, T, int> comparerFunc)
		{
			this.Sort(comparerFunc);
		}

		public SmartObservableCollection(IEnumerable<T> collection, Func<T, T, int> comparerFunc)
			: base(collection)
		{
			this.Sort(comparerFunc);
		}

		public SmartObservableCollection(List<T> collection, Func<T, T, int> comparerFunc)
			: base(collection)
		{
			this.Sort(comparerFunc);
		}

		/// <summary>
		/// This event is overridden CollectionChanged event of the observable collection.
		/// </summary>
		public override event NotifyCollectionChangedEventHandler CollectionChanged;

		/// <summary>
		/// This private variable holds the flag to
		/// turn on and off the collection changed notification.
		/// </summary>
		protected bool IsChangeNotificationSuspended { get; private set; }

		public new virtual void Add(T item)
		{
			lock (instance)
			{
				base.Add(item);
			}
		}

		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			lock (instance)
			{
				return GetEnumerator();
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			lock (instance)
			{
				return ((IEnumerable<T>)this).GetEnumerator();
			}
		}

		/// <summary>
		/// This method adds the given generic list of items
		/// as a range into current collection by casting them as type T.
		/// It then notifies once after all items are added.
		/// </summary>
		/// <param name="items">The source collection.</param>
		public virtual void AddRange(IEnumerable<T> items)
		{
			lock (instance)
			{
				SuspendCollectionChangeNotification();

				foreach (var item in items)
				{
					base.Add(item);
				}

				NotifyChanges();
			}
		}

		/// <summary>
		/// Raises collection change event.
		/// </summary>
		public void NotifyChanges()
		{
			lock (instance)
			{
				ResumeCollectionChangeNotification();

				var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);

				OnCollectionChanged(arg);
			}
		}

		/// <summary>
		/// This method removes the given generic list of items as a range
		/// into current collection by casting them as type T.
		/// It then notifies once after all items are removed.
		/// </summary>
		/// <param name="collection">The source collection.</param>
		public void RemoveItems(IEnumerable<T> collection)
		{
			lock (instance)
			{
				SuspendCollectionChangeNotification();

				foreach (var item in collection)
				{
					Remove(item);
				}

				NotifyChanges();
			}
		}

		/// <summary>
		/// Resumes collection changed notification.
		/// </summary>
		public void ResumeCollectionChangeNotification()
		{
			lock (instance)
			{
				IsChangeNotificationSuspended = false;
			}
		}

		/// <summary>
		/// Suspends collection changed notification.
		/// </summary>
		public void SuspendCollectionChangeNotification()
		{
			lock (instance)
			{
				IsChangeNotificationSuspended = true;
			}
		}

		public void ReplaceOrAdd(T item)
		{
			lock (instance)
			{
				var index = IndexOf(item);

				if (index >= 0)
				{
					SetItem(index, item);
					return;
				}

				Add(item);
			}
		}

		protected override void SetItem(int index, T item)
		{
			lock (instance)
			{
				CheckReentrancy();
				base.SetItem(index, item);

				OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
			}
		}

		/// <summary>
		/// This collection changed event performs thread safe event raising.
		/// </summary>
		/// <param name="e">The event argument.</param>
		protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
		{
			// Recommended is to avoid reentry
			// in collection changed event while collection
			// is getting changed on other thread.
			using (BlockReentrancy())
			{
				if (IsChangeNotificationSuspended)
				{
					return;
				}

				var eventHandler = CollectionChanged;

				if (eventHandler == null)
				{
					return;
				}

				var delegates = eventHandler.GetInvocationList();

				foreach (NotifyCollectionChangedEventHandler handler in delegates)
				{
					// If the subscriber is a DispatcherObject and different thread
					var dispatcherObject = handler.Target as DispatcherObject;

					if (dispatcherObject != null && !dispatcherObject.CheckAccess())
					{
						// Invoke handler in the target dispatcher's thread
						// asynchronously for better responsiveness.
						dispatcherObject.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, handler, this, e);
					}
					else
					{
						// Execute handler as is
						handler(this, e);
					}
				}
			}
		}
	}
}